package com.xvyy.tingshu.search.service.impl;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.Hit;
import com.alibaba.fastjson.JSONObject;
import com.xvyy.tingshu.common.constant.RedisConstant;
import com.xvyy.tingshu.common.util.PinYinUtils;
import com.xvyy.tingshu.search.factory.ScheduledTaskThreadFactory;
import com.xvyy.tingshu.search.repository.SuggestInfoRepository;
import com.xvyy.tingshu.search.runnable.RebuildDistributedBloomFilterRunnable;
import com.xvyy.tingshu.search.thread.ExpireThread;
import com.xvyy.tingshu.vo.search.AlbumInfoIndexVo;
import com.google.common.hash.BloomFilter;
import com.xvyy.tingshu.annotation.Cacheable;
import jakarta.annotation.PostConstruct;
import lombok.SneakyThrows;

import org.jetbrains.annotations.NotNull;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.data.elasticsearch.core.suggest.Completion;

import com.xvyy.tingshu.album.client.AlbumInfoFeignClient;
import com.xvyy.tingshu.common.execption.TsException;
import com.xvyy.tingshu.common.result.Result;
import com.xvyy.tingshu.model.album.AlbumInfo;
import com.xvyy.tingshu.model.album.BaseCategoryView;
import com.xvyy.tingshu.model.search.AttributeValueIndex;
import com.xvyy.tingshu.model.search.SuggestIndex;
import com.xvyy.tingshu.user.client.UserInfoFeignClient;
import com.xvyy.tingshu.vo.album.AlbumStatVo;
import com.xvyy.tingshu.vo.user.UserInfoVo;

import java.util.*;
import java.util.concurrent.*;
import java.util.stream.Collectors;

import com.xvyy.tingshu.model.search.AlbumInfoIndex;
import com.xvyy.tingshu.search.repository.AlbumInfoRepository;
import com.xvyy.tingshu.search.service.ItemService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class ItemServiceImpl implements ItemService {

    @Autowired
    private AlbumInfoRepository albumInfoRepository;
    @Autowired
    private AlbumInfoFeignClient albumInfoFeignClient;
    @Autowired
    private UserInfoFeignClient userInfoFeignClient;
    @Autowired
    private SuggestInfoRepository suggestInfoRepository;
    @Autowired
    private ThreadPoolExecutor threadPoolExecutor;
    @Autowired
    private ElasticsearchClient elasticsearchClient;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private RBloomFilter rBloomFilter;
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private ItemServiceImpl itemService;//开启循环依赖

    static ExecutorService executorService = Executors.newFixedThreadPool(4);
    Map<String, Object> localCache = new HashMap<>();
    Map<Thread, String> deepThreadStringMap = new HashMap<>();
    ThreadLocal<String> deepAcquireLockTokenThreadLocal = new ThreadLocal<>();
    BloomFilter<Long> longBloomFilter = null;

    /**
     * 3.  线程池 + CountDownLatch
     * newThreadPool();
     * countDownLatch.countDown();
     * countDownLatch.await();
     * 第一次耗时间： 370ms
     * 之后平均： 38ms
     *
     * @param albumId
     */
    // 创建4个线程的线程池
    // submit: 提交一个任务，返回一个future对象，可以获取到future对象的执行结果
    // execute: 提交一个任务，没有返回值，主要用于执行Runnable任务
    @Override
    public void onAlbumEs(Long albumId) {
        CountDownLatch countDownLatch = new CountDownLatch(4);// 计数器初始化为4，表示有4个线程需要等待
        log.info("开始执行onAlbumEs方法，albumId={}", albumId);
        Long startTime = System.currentTimeMillis();
        // 0. 构建索引文档
        AlbumInfoIndex albumInfoIndex = new AlbumInfoIndex();
        // 1. 获取专辑基本信息线程
        Future<Long> longFuture = executorService.submit(new Callable<Long>() {
            @Override
            public Long call() throws Exception {
                // 1. 远调查询专辑微服务 获取专辑基本信息
                Result<AlbumInfo> albumInfoResult = albumInfoFeignClient.getAlbumInfo(albumId);
                if (albumInfoResult == null) {
                    throw new TsException(201, "远调查询专辑微服务获取专辑基本信息失败");
                }
                AlbumInfo albumInfoResultData = albumInfoResult.getData();
                //拷贝基本信息到alInfoIndex对象中
                BeanUtils.copyProperties(albumInfoResultData, albumInfoIndex);
                // 设置album_info表中的字段
//        albumInfoIndex.setId(albumId);// 文档id
//        albumInfoIndex.setAlbumTitle(albumInfoResultData.getAlbumTitle());// 专辑标题
//        albumInfoIndex.setAlbumIntro(albumInfoResultData.getAlbumIntro());// 专辑简介
//        albumInfoIndex.setCoverUrl(albumInfoResultData.getCoverUrl());// 专辑封面图片
//        albumInfoIndex.setIncludeTrackCount(albumInfoResultData.getIncludeTrackCount());// 专辑包含的曲目数
//        albumInfoIndex.setPayType(albumInfoResultData.getPayType());// 付费类型
                albumInfoIndex.setIsFinished(albumInfoResultData.getIsFinished().toString());// 是否完结
                List<AttributeValueIndex> attributeValueIndexList = albumInfoResultData.getAlbumAttributeValueVoList().stream().map(albumAttributeValue -> {
                    AttributeValueIndex attributeValueIndex = new AttributeValueIndex();
                    attributeValueIndex.setAttributeId(albumAttributeValue.getAttributeId());
                    attributeValueIndex.setValueId(albumAttributeValue.getValueId());
                    return attributeValueIndex;
                }).collect(Collectors.toList());
                albumInfoIndex.setAttributeValueIndexList(attributeValueIndexList);// 专辑标签信息
                albumInfoIndex.setCreateTime(new Date());// 创建时间
                countDownLatch.countDown();// 计数器减1
                return albumInfoResultData.getUserId();
            }
        });

        // 2. 获取用户的基本信息线程
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    // longFuture.get() 会阻塞当前线程，直到future执行完成并返回结果
                    Long userId = longFuture.get();
                    // 2. 远调查询用户微服务 获取用户的基本信息
                    // user_info表中的字段
                    Result<UserInfoVo> userInfoVoResult = userInfoFeignClient.getUserInfoVo(userId);
                    if (userInfoVoResult == null) {
                        throw new TsException(201, "远调查询用户微服务获取主播信息失败");
                    }
                    UserInfoVo userInfoVoResultData = userInfoVoResult.getData();
                    albumInfoIndex.setAnnouncerName(userInfoVoResultData.getNickname());// 主播名称

                    countDownLatch.countDown();// 计数器减1

                } catch (Exception e) {
                    log.error("线程阻塞时出现异常，异常信息：{}", e.getMessage());
                }
            }
        });

        // 3. 获取专辑的分类信息线程
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                // 3. 远调查询专辑微服务 获取专辑的分类信息
                Result<BaseCategoryView> baseCategoryViewResult = albumInfoFeignClient.getAlbumInfoCategory(albumId);
                if (baseCategoryViewResult == null) {
                    throw new TsException(201, "远调查询专辑微服务获取专辑的分类信息失败");
                }
                BaseCategoryView baseCategoryViewData = baseCategoryViewResult.getData();
                // base_category1、base_category2、base_category3表中的字段
                albumInfoIndex.setCategory1Id(baseCategoryViewData.getCategory1Id());
                albumInfoIndex.setCategory2Id(baseCategoryViewData.getCategory2Id());
                albumInfoIndex.setCategory3Id(baseCategoryViewData.getCategory3Id());
                countDownLatch.countDown();// 计数器减1
            }
        });

        // 4. 获取专辑的统计信息线程
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                // 4. 远调查询专辑微服务 获取专辑的统计信息
                Result<AlbumStatVo> albumStatVoResult = albumInfoFeignClient.getAlbumInfoStat(albumId);
                AlbumStatVo albumStatVoData = albumStatVoResult.getData();
                if (albumStatVoData == null) {
                    throw new TsException(201, "搜索微服务调用专辑微服务获取专辑统计信息失败");
                }
                // album_stat表中的字段
                Integer playStatNum = albumStatVoData.getPlayStatNum();
                Integer subscribeStatNum = albumStatVoData.getSubscribeStatNum();
                Integer buyStatNum = albumStatVoData.getBuyStatNum();
                Integer commentStatNum = albumStatVoData.getCommentStatNum();
                albumInfoIndex.setPlayStatNum(playStatNum);
                albumInfoIndex.setSubscribeStatNum(subscribeStatNum);
                albumInfoIndex.setBuyStatNum(buyStatNum);
                albumInfoIndex.setCommentStatNum(commentStatNum);

                // 热度评分字段 权重计算 根据专辑的播放量、订阅数、购买数、评论数 综合评分
//        Double hotScore = playStatNum * 0.4 + subscribeStatNum * 0.1 + buyStatNum * 0.3 + commentStatNum * 0.2;
                Double hotScore = new Random().nextDouble();
                albumInfoIndex.setHotScore(hotScore);

                countDownLatch.countDown();// 计数器减1
            }
        });

        // 等待所有线程执行完成
        // 5. 将文档对象保存到ES中
        try {
            countDownLatch.await();
            // 保存到ES
            AlbumInfoIndex flag = albumInfoRepository.save(albumInfoIndex);//tomcat线程池执行

            Long endTime = System.currentTimeMillis();
            log.info("专辑：{}存储到ES中{}耗时：{}ms", albumId, flag != null ? "成功" : "失败", endTime - startTime);

        } catch (InterruptedException e) {
            log.error("等待线程执行完成时出现异常，异常信息：{}", e.getMessage());
        }

        // 6. 创建一个文档提示文档  放到ES索引库中
        SuggestIndex suggestIndex = new SuggestIndex();

        String albumTitle = albumInfoIndex.getAlbumTitle();
        Completion completionChinese = new Completion(new String[]{albumTitle});
        Completion completionPinyin = new Completion(new String[]{PinYinUtils.toHanyuPinyin(albumTitle)});
        Completion completionSequence = new Completion(new String[]{PinYinUtils.getFirstLetter(albumTitle)});
        suggestIndex.setId(albumId.toString());// 文档id
        suggestIndex.setTitle(albumTitle);// 提示内容
        suggestIndex.setKeyword(completionChinese);// 汉语补全
        suggestIndex.setKeywordPinyin(completionPinyin);// 拼音补全
        suggestIndex.setKeywordSequence(completionSequence);// 拼音首字母补全
        // 保存到ES中
        suggestInfoRepository.save(suggestIndex);

    }

    @Override
    public void offAlbumEs(Long albumId) {
        albumInfoRepository.deleteById(albumId);
    }

    @Override
    public void onAlbumEsAll() {
        //TODO

        // 1. 查询数据库中所有的专辑信息

        // 2. 遍历查询结果

        // 3. 构建索引文档对象

        // 4. 保存到ES

    }

    @Override
    @SneakyThrows
    public Map<String, Object> getAlbumInfoByAlbumId(Long albumId) {
    /*
        return getAlbumInfoByDb(albumId);//直接数据库查询
        return osLocalCache(albumId);//本地缓存
        return opsDistributedCache(albumId);//分布式缓存
        return opsDistributedCacheAndCacheHitV1(albumId);//死锁
        return opsDistributedCacheAndCacheHitV2_1(albumId);
        return opsDistributedCacheAndCacheHitV2_2(albumId);
        return opsDistributedCacheAndCacheHitV3(albumId);
        return opsDistributedCacheAndCacheHitV4(albumId);
        return opsDistributedCacheAndCacheHitElseV1(albumId);
        return opsDistributedCacheAndCacheHitElseV2(albumId);
        return opsDistributedCacheAndCacheHitAndRenewal(albumId);
        return osLocalCacheAndBoomFilter(albumId);//布隆过滤器+本地缓存
        return opsDistributedCacheAndBloomFilter(albumId);//布隆过滤器+redis分布式缓存
        return OpsDistributedCacheBloomFilterRedissonVersion(albumId);//布隆过滤器+redisson分布式缓存
        */
        return getAlbumInfoByDb(albumId);
    }

    private Map<String, Object> OpsDistributedCacheBloomFilterRedissonVersion(Long albumId) {

        // 1. 定义变量
        // 1.1 定义缓存key
        String cacheKey = RedisConstant.CACHE_INFO_PREFIX + albumId;
        // 1.2 定义锁key
        String lockKey = RedisConstant.CACHE_LOCK_SUFFIX + albumId;
        // 2. 查询分布式布隆过滤器
        boolean bloomContainsFlag = rBloomFilter.contains(albumId);
        // 2 判断是否存在
        if (!bloomContainsFlag) {
            // 2.1 布隆说有，直接返回
            return null;
        }
        // 2.2 布隆说没有
        // 3. 查询分布式缓存
        String jsonStringFromCache = stringRedisTemplate.opsForValue().get(cacheKey);
        if (!StringUtils.isEmpty(jsonStringFromCache)) {
            // 3.1 缓存有 直接返回
            return JSONObject.parseObject(jsonStringFromCache, Map.class);
        }
        // 3.2 缓存没有
        // 4. 获取锁对象
        RLock lock = redissonClient.getLock(lockKey);
        // 4.1 加锁 解决缓存击穿问题
        boolean aBoolean = lock.tryLock();
        if (aBoolean) {
            //4.2 加锁成功
            try {
                // 4.2.1 回源查DB
                // 4.2.2 查DB
                Map<String, Object> albumInfoByDb = getAlbumInfoByDb(albumId);
                long ttl = 0L;
                if (albumInfoByDb == null || albumInfoByDb.size() == 0) {
                    ttl = 60L;//30min
                } else {
                    ttl = 60 * 24 * 7L;
                }
                // 4.2.3 将查询结果写入到缓存中
                stringRedisTemplate.opsForValue().set(cacheKey, JSONObject.toJSONString(albumInfoByDb), ttl, TimeUnit.SECONDS);
                return albumInfoByDb;
            } finally {
                //4.3 解锁
                lock.unlock();
            }
        } else {
            // 5. 没抢到锁（等待同步数据时间）
            try {
                Thread.sleep(200L);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            String resultFromCache = stringRedisTemplate.opsForValue().get(cacheKey);
            // case1: null -> 空对象 不会报错
            // case2: {} -> {}对象
            // case3: {key:value} -> Map对象
            return JSONObject.parseObject(resultFromCache, Map.class);
        }
    }

    /**
     * 1. 定时任务 +线程池
     * 2. 延时方式 + 线程池 + 嵌套自己实现定时任务
     */
    @PostConstruct
    public void rebuildBloomFilter() {
        // 1. 定时任务+线程池重建布隆过滤器
/*        // 1.1 定义一个定时任务线程池
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
        // 1.2 定义定时任务
        scheduledExecutorService.scheduleWithFixedDelay(
                new RebuildDistributedBloomFilterRunnable(redissonClient , itemService , stringRedisTemplate),
                0, 1, TimeUnit.DAYS);// 每天执行一次*/
/*        ScheduledTaskThreadFactory.getInstance().executeTaskScheduleWithFixedDelay(
                new RebuildDistributedBloomFilterRunnable(redissonClient, itemService, stringRedisTemplate),
                0, 1L, TimeUnit.DAYS);*/
        // 2. 延时方式+线程池重建布隆过滤器
        // 2.1 定义一个延时任务线程池
/*        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
        scheduledExecutorService.schedule(
                new RebuildDistributedBloomFilterRunnable(redissonClient, itemService, stringRedisTemplate),
                1, TimeUnit.MINUTES);//延迟1分钟执行*/
        ScheduledTaskThreadFactory.getInstance().executeTaskSchedule(
                new RebuildDistributedBloomFilterRunnable(redissonClient, itemService, stringRedisTemplate),
                1L, TimeUnit.MINUTES);//延迟1分钟执行
/*        ScheduledTaskThreadFactory instance = ScheduledTaskThreadFactory.getInstance();
        long taskBeginTime = instance.timeDifference(System.currentTimeMillis());
        instance.executeTaskSchedule(
                new RebuildDistributedBloomFilterRunnable(redissonClient, itemService, stringRedisTemplate),
                taskBeginTime, TimeUnit.MILLISECONDS);//今天开始的每七天的凌晨2点执行一次*/
    }

    /**
     * @PostConstruct + @Service
     * 初始化本地布隆过滤器
     */
/*    @PostConstruct
    public void initLocalBloomFilter() {
        // 1. 定义布隆过滤器对象
        Funnel<Long> longFunnel = Funnels.longFunnel();
        longBloomFilter = BloomFilter.create(longFunnel, 1000000, 0.01);
        // 2. 从数据库中查询所有专辑的id
        List<Long> albumInfoIdList = getAlbumInfoIdList();
        if (!CollectionUtils.isEmpty(albumInfoIdList)) {
            // 3. 初始化
            for (Long albumInfoId : albumInfoIdList) {
                longBloomFilter.put(albumInfoId);
            }
            log.info("初始化本地布隆过滤器完成，当前布隆过滤器的元素数量：{}", longBloomFilter.approximateElementCount());
        }
    }*/

    /**
     * 查询数据库中所有的专辑信息
     *
     * @return
     */
    public List<Long> getAlbumInfoIdList() {
        Result<List<Long>> albumInfoIdListResult = albumInfoFeignClient.getAlbumInfoIdList();
        List<Long> albumInfoIdListData = albumInfoIdListResult.getData();
        if (!CollectionUtils.isEmpty(albumInfoIdListData)) {
            return albumInfoIdListData;
        }
        return new ArrayList<>();
    }

    /**
     * opsDistributedCacheAndCacheHitAndRenewal
     * redis版本的分布式自旋可重入锁+递归+threadLocal+双缓存机制的缓存击穿解决方案 + 业务没结束自动续期
     *
     * @param albumId
     * @return
     * @throws InterruptedException
     * @throws ExecutionException
     */
    private Map opsDistributedCacheAndCacheHitAndRenewal(Long albumId) throws InterruptedException, ExecutionException {

        // 1. 定义缓存和锁的key
        // 1.1 定义缓存的key
        String cacheKey = RedisConstant.CACHE_INFO_PREFIX + albumId;
        // 1.2 定义锁的key
        String lockKey = RedisConstant.CACHE_LOCK_SUFFIX + albumId;
        // 1.3 锁的标识位
        Boolean lockFlag = false;
        // 1.4 定义token值
        String token = null;

        // 2. 查询缓存
        String jsonFromCache = stringRedisTemplate.opsForValue().get(cacheKey);
        // 3. 判断缓存是否命中
        if (!StringUtils.isEmpty(jsonFromCache)) {
            // 3.1 缓存命中 直接返回
            return JSONObject.parseObject(jsonFromCache);
        }

        // 4. 缓存未命中 回源
//        String deepAcquireLockFlag = deepThreadStringMap.get(Thread.currentThread());
        // 4.1 从ThreadLocal中获取
        String deepAcquireLockFlag = deepAcquireLockTokenThreadLocal.get();
        if (!StringUtils.isEmpty(deepAcquireLockFlag)) {
            // 4.2 获取到锁成功
            lockFlag = true;
            token = deepAcquireLockFlag;
        } else {
            // 4.2 获取到锁失败，重新获取一个token值
            // 生成一个token：锁的唯一标识
            token = UUID.randomUUID().toString().replaceAll("-", "");
            lockFlag = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, token, 60, TimeUnit.SECONDS);
        }
        // 5. 抢锁成功
        if (lockFlag) {
            // 5.1 启动一个续期线程完成续期操作
            ExpireThread expireThread = new ExpireThread(stringRedisTemplate);
            expireThread.expire(lockKey, 10L, 60L);
            Map<String, Object> albumInfoFromDb = new HashMap<>();
            try {
                // 5.2 抢到锁，回源，直接到数据库中查询
                albumInfoFromDb = getAlbumInfoByDb(albumId);
                // 5.3 同步数据到redis中
                stringRedisTemplate.opsForValue().set(cacheKey, JSONObject.toJSONString(albumInfoFromDb));
                // 5.4 释放锁
                //使用Lua脚本释放锁
                String script = "if redis.call('GET',KEYS[1])==ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
                Long execute = stringRedisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(lockKey), token);
                if (execute == 0) {
                    log.error("删除锁失败");
                } else {
                    log.info("删除锁成功");
                }
                // 5.5 移除token值 防止内存泄漏问题
                deepAcquireLockTokenThreadLocal.remove();
            } finally {
                // 5.6 中断续期线程
                expireThread.expireStop();
            }
            // 5.7 返回数据
            return albumInfoFromDb;
        } else {
            // 6. 抢不到锁
            // 6. 1 解决正常情况
            long timeout = 200;//压测等待时间
            Thread.sleep(timeout);
            String result = stringRedisTemplate.opsForValue().get(cacheKey);
            if (!StringUtils.isEmpty(result)) {
                return JSONObject.parseObject(result, Map.class);
            } else {
                // 6.2 解决极端情况
                // 等待锁释放了 才能去递归，否则不递归，自旋抢锁
                // 自旋抢锁，解决无效递归，尽可能减少栈溢出
                while (true) {
                    // 6.3 双缓存策略
                    if (!StringUtils.isEmpty(stringRedisTemplate.opsForValue().get(cacheKey))) {
                        // 缓存命中 直接返回
                        return JSONObject.parseObject(stringRedisTemplate.opsForValue().get(cacheKey));
                    }
                    // 6.4 尝试抢锁 极端情况下，可能cpu打满
                    Boolean attenptAcquireLock = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, token, 60, TimeUnit.SECONDS);
                    if (attenptAcquireLock) {
//                        deepThreadStringMap.put(Thread.currentThread(), token);
                        deepAcquireLockTokenThreadLocal.set(token);
                        break;
                    }
                }
                // 7. 递归查询缓存
                return opsDistributedCacheAndCacheHitAndRenewal(albumId);
            }
        }

    }


    /**
     * opsDistributedCacheAndCacheHitElseV2
     * redis版本的分布式自旋可重入锁+递归+threadLocal+双缓存机制的缓存击穿解决方案
     *
     * @param albumId
     * @return
     * @throws InterruptedException
     * @throws ExecutionException
     */
    private Map opsDistributedCacheAndCacheHitElseV2(Long albumId) throws InterruptedException, ExecutionException {

        // 1. 定义缓存的key
        String cacheKey = RedisConstant.CACHE_INFO_PREFIX + albumId;
        // 2. 定义锁的key
        String lockKey = RedisConstant.CACHE_LOCK_SUFFIX + albumId;
        Boolean lockFlag = false;
        String token = null;

        // 3. 查询缓存
        String jsonFromCache = stringRedisTemplate.opsForValue().get(cacheKey);
        if (!StringUtils.isEmpty(jsonFromCache)) {
            // 3.1 缓存命中 直接返回
            return JSONObject.parseObject(jsonFromCache);
        }
        // 4 缓存未命中 回源
//        String deepAcquireLockFlag = deepThreadStringMap.get(Thread.currentThread());
        String deepAcquireLockFlag = deepAcquireLockTokenThreadLocal.get();
        if (!StringUtils.isEmpty(deepAcquireLockFlag)) {
            // 4.1 抢到锁，回源
            lockFlag = true;
            token = deepAcquireLockFlag;
        } else {
            // 生成一个token：锁的唯一标识
            // 4.2 未抢到锁，递归查询缓存
            token = UUID.randomUUID().toString().replaceAll("-", "");
            lockFlag = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, token, 60, TimeUnit.SECONDS);
        }
        if (lockFlag) {
            // 4.1 抢到锁，回源，直接到数据库中查询
            Map<String, Object> albumInfoFromDb = getAlbumInfoByDb(albumId);
            // 4.2 同步数据到redis中
            stringRedisTemplate.opsForValue().set(cacheKey, JSONObject.toJSONString(albumInfoFromDb));
            // 4.3 释放锁
            //使用Lua脚本释放锁
            String script = "if redis.call('GET',KEYS[1])==ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
            Long execute = stringRedisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(lockKey), token);
            if (execute == 0) {
                log.error("删除锁失败");
            } else {
                log.info("删除锁成功");
            }
            // 防止内存泄漏问题，用完之后要移除ThreadLocal中的数据
            deepAcquireLockTokenThreadLocal.remove();
            // 4.4 返回查询结果
            return albumInfoFromDb;
        } else {
            // 5. 抢不到锁
            // 等待锁释放了 才能去递归，否则不递归，自旋抢锁
            // 自旋抢锁，解决无效递归，尽可能减少栈溢出
            // 5. 1 解决正常情况
            long timeout = 200;//压测等待时间
            Thread.sleep(timeout);
            String result = stringRedisTemplate.opsForValue().get(cacheKey);
            if (!StringUtils.isEmpty(result)) {
                return JSONObject.parseObject(result, Map.class);
            } else {
                // 5.2 解决极端情况
                // 等待锁释放了 才能去递归，否则不递归，自旋抢锁
                // 自旋抢锁，解决无效递归，尽可能减少栈溢出
                while (true) {
                    // 双缓存策略
                    if (!StringUtils.isEmpty(stringRedisTemplate.opsForValue().get(cacheKey))) {
                        // 缓存命中 直接返回
                        return JSONObject.parseObject(stringRedisTemplate.opsForValue().get(cacheKey));
                    }

                    // 尝试抢锁 极端情况下，可能cpu打满
                    Boolean attemptAcquireLock = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, token, 60, TimeUnit.SECONDS);
                    if (attemptAcquireLock) {
//                        deepThreadStringMap.put(Thread.currentThread(), token);
                        deepAcquireLockTokenThreadLocal.set(token);
                        break;
                    }
                }
                return opsDistributedCacheAndCacheHitElseV2(albumId);
            }
        }

    }

    private Map opsDistributedCacheAndCacheHitElseV1(Long albumId) throws InterruptedException, ExecutionException {
        // 1. 定义缓存的key
        String cacheKey = RedisConstant.CACHE_INFO_PREFIX + albumId;
        // 2. 定义锁的key
        String lockKey = RedisConstant.CACHE_LOCK_SUFFIX + albumId;

        // 3. 查询缓存
        String jsonFromCache = stringRedisTemplate.opsForValue().get(cacheKey);
        if (!StringUtils.isEmpty(jsonFromCache)) {
            // 3.1 缓存命中 直接返回
            return JSONObject.parseObject(jsonFromCache, Map.class);
        }
        // 4 缓存未命中 加锁
        // 生成一个token：锁的唯一标识
        String token = UUID.randomUUID().toString().replace("-", "");
        Boolean lockFlag = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, token, 10, TimeUnit.SECONDS);
        if (lockFlag) {
            // 4.1 抢到锁，回源，直接到数据库中查询
            Map<String, Object> albumInfoFromDb = getAlbumInfoByDb(albumId);
            // 4.2 同步数据到redis中
            stringRedisTemplate.opsForValue().set(cacheKey, JSONObject.toJSONString(albumInfoFromDb));
            // 4.3 释放锁
            //使用Lua脚本释放锁
            String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
            Long execute = stringRedisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(lockKey), token);
            if (execute == 0) {
                log.error("释放锁失败");
            } else {
                log.info("释放锁成功");
            }

            // 4.4 返回查询结果
            return albumInfoFromDb;
        } else {
            // 等待锁释放了 才能去递归，否则不递归，自旋抢锁
            // 自旋抢锁，解决无效递归，尽可能减少栈溢出
            long timeout = 200;//压测等待时间
            Thread.sleep(timeout);
            String result = stringRedisTemplate.opsForValue().get(cacheKey);
            if (!StringUtils.isEmpty(result)) {
                return JSONObject.parseObject(result, Map.class);
            } else {
                while (true) {
                    // 双缓存策略
                    if (!StringUtils.isEmpty(stringRedisTemplate.opsForValue().get(cacheKey))) {
                        // 缓存命中 直接返回
                        return JSONObject.parseObject(stringRedisTemplate.opsForValue().get(cacheKey));
                    }

                    // 尝试抢锁 极端情况下，可能cpu打满
                    Boolean attenptAcquireLock = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, token, 10, TimeUnit.SECONDS);
                    if (attenptAcquireLock) {
                        break;
                    }
                }
                return opsDistributedCacheAndCacheHitElseV1(albumId);
            }
        }

    }

    private Map opsDistributedCacheAndCacheHitV4(Long albumId) throws InterruptedException, ExecutionException {
        // 1. 定义缓存的key
        String cacheKey = RedisConstant.CACHE_INFO_PREFIX + albumId;
        // 2. 定义锁的key
        String lockKey = RedisConstant.CACHE_LOCK_SUFFIX + albumId;

        // 3. 查询缓存
        String jsonFromCache = stringRedisTemplate.opsForValue().get(cacheKey);
        if (!StringUtils.isEmpty(jsonFromCache)) {
            // 3.1 缓存命中 直接返回
            return JSONObject.parseObject(jsonFromCache, Map.class);
        }
        // 4 缓存未命中 加锁
        // 生成一个token：锁的唯一标识
        String token = UUID.randomUUID().toString().replace("-", "");
        Boolean lockFlag = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, token, 10, TimeUnit.SECONDS);
        if (lockFlag) {
            // 4.1 抢到锁，回源，直接到数据库中查询
            Map<String, Object> albumInfoFromDb = getAlbumInfoByDb(albumId);
            // 4.2 同步数据到redis中
            stringRedisTemplate.opsForValue().set(cacheKey, JSONObject.toJSONString(albumInfoFromDb));
            // 4.3 释放锁
            //使用Lua脚本释放锁
            String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
            Long execute = stringRedisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(lockKey), token);
            if (execute == 0) {
                log.error("释放锁失败");
            } else {
                log.info("释放锁成功");
            }

            // 4.4 返回查询结果
            return albumInfoFromDb;
        } else {
            // 4.2 未抢到锁，压测等待时间
            long timeout = 200;
            Thread.sleep(timeout);
//            String result = stringRedisTemplate.opsForValue().get(cacheKey);
//            return JSONObject.parseObject(result, Map.class);
            // 4.4 递归查询
            return opsDistributedCacheAndCacheHitV4(albumId);
        }
    }

    private Map opsDistributedCacheAndCacheHitV3(Long albumId) throws InterruptedException, ExecutionException {
        // 1. 定义缓存的key
        String cacheKey = RedisConstant.CACHE_INFO_PREFIX + albumId;
        // 2. 定义锁的key
        String lockKey = RedisConstant.CACHE_LOCK_SUFFIX + albumId;

        // 3. 查询缓存
        String jsonFromCache = stringRedisTemplate.opsForValue().get(cacheKey);
        if (!StringUtils.isEmpty(jsonFromCache)) {
            // 3.1 缓存命中 直接返回
            return JSONObject.parseObject(jsonFromCache, Map.class);
        }
        // 4 缓存未命中 加锁
        // 生成一个token：锁的唯一标识
        String token = UUID.randomUUID().toString().replace("-", "");
        Boolean lockFlag = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, token, 10, TimeUnit.SECONDS);
        if (lockFlag) {
            // 4.1 抢到锁，回源，直接到数据库中查询
            Map<String, Object> albumInfoFromDb = getAlbumInfoByDb(albumId);
            // 4.2 同步数据到redis中
            stringRedisTemplate.opsForValue().set(cacheKey, JSONObject.toJSONString(albumInfoFromDb));
            // 4.3 释放锁
            // token 和 redis中的token 比较，防止误删除其他线程的锁
            String redisToken = stringRedisTemplate.opsForValue().get(lockKey);
            if (token.equals(redisToken)) {
                stringRedisTemplate.delete(lockKey);

            }
            // 4.4 返回查询结果
            return albumInfoFromDb;
        } else {
            // 4.2 未抢到锁，压测等待时间
            long timeout = 200;
            Thread.sleep(timeout);
//            String result = stringRedisTemplate.opsForValue().get(cacheKey);
//            return JSONObject.parseObject(result, Map.class);
            // 4.4 递归查询
            return opsDistributedCacheAndCacheHitV3(albumId);
        }
    }

    private Map opsDistributedCacheAndCacheHitV2_2(Long albumId) throws InterruptedException, ExecutionException {
        // 1. 定义缓存的key
        String cacheKey = RedisConstant.CACHE_INFO_PREFIX + albumId;
        // 2. 定义锁的key
        String lockKey = RedisConstant.CACHE_LOCK_SUFFIX + albumId;

        // 3. 查询缓存
        String jsonFromCache = stringRedisTemplate.opsForValue().get(cacheKey);
        if (!StringUtils.isEmpty(jsonFromCache)) {
            // 3.1 缓存命中 直接返回
            return JSONObject.parseObject(jsonFromCache, Map.class);
        }
        // 4 缓存未命中 加锁
        Boolean lockFlag = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);
        if (lockFlag) {
            // 4.1 抢到锁，回源，直接到数据库中查询
            Map<String, Object> albumInfoFromDb = getAlbumInfoByDb(albumId);
            // 4.2 同步数据到redis中
            stringRedisTemplate.opsForValue().set(cacheKey, JSONObject.toJSONString(albumInfoFromDb));
            // 4.3 释放锁
            stringRedisTemplate.delete(lockKey);
            // 4.4 返回查询结果
            return albumInfoFromDb;
        } else {
            // 4.2 未抢到锁，压测等待时间
            long timeout = 200;
            Thread.sleep(timeout);
//            String result = stringRedisTemplate.opsForValue().get(cacheKey);
//            return JSONObject.parseObject(result, Map.class);

            // 4.4 递归查询
            return opsDistributedCacheAndCacheHitV2_2(albumId);
        }
    }

    private Map opsDistributedCacheAndCacheHitV2_1(Long albumId) throws InterruptedException, ExecutionException {
        // 1. 定义缓存的key
        String cacheKey = RedisConstant.CACHE_INFO_PREFIX + albumId;
        // 2. 定义锁的key
        String lockKey = RedisConstant.CACHE_LOCK_SUFFIX + albumId;

        // 3. 查询缓存
        String jsonFromCache = stringRedisTemplate.opsForValue().get(cacheKey);
        if (!StringUtils.isEmpty(jsonFromCache)) {
            // 3.1 缓存命中 直接返回
            return JSONObject.parseObject(jsonFromCache, Map.class);
        }
        // 4 缓存未命中 加锁
        Boolean lockFlag = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "1");
        if (lockFlag) {
            stringRedisTemplate.expire(lockKey, 10, TimeUnit.SECONDS);
            // 4.1 抢到锁，回源，直接到数据库中查询
            Map<String, Object> albumInfoFromDb = getAlbumInfoByDb(albumId);
            // 4.2 同步数据到redis中
            stringRedisTemplate.opsForValue().set(cacheKey, JSONObject.toJSONString(albumInfoFromDb));
            // 4.3 释放锁
            stringRedisTemplate.delete(lockKey);
            // 4.4 返回查询结果
            return albumInfoFromDb;
        } else {
            // 4.2 未抢到锁，压测等待时间
            long timeout = 200;
            Thread.sleep(timeout);
//            String result = stringRedisTemplate.opsForValue().get(cacheKey);
//            return JSONObject.parseObject(result, Map.class);

            // 4.4 递归查询
            return opsDistributedCacheAndCacheHitV2_1(albumId);
        }
    }

    private Map opsDistributedCacheAndCacheHitV1(Long albumId) throws InterruptedException, ExecutionException {
        // 1. 定义缓存的key
        String cacheKey = RedisConstant.CACHE_INFO_PREFIX + albumId;
        // 2. 定义锁的key
        String lockKey = RedisConstant.CACHE_LOCK_SUFFIX + albumId;

        // 3. 查询缓存
        String jsonFromCache = stringRedisTemplate.opsForValue().get(cacheKey);
        if (!StringUtils.isEmpty(jsonFromCache)) {
            // 3.1 缓存命中 直接返回
            return JSONObject.parseObject(jsonFromCache, Map.class);
        }
        // 4 缓存未命中 加锁
        Boolean lockFlag = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "1");
        if (Boolean.TRUE.equals(lockFlag)) {
            // 4.1 抢到锁，回源，直接到数据库中查询
            Map<String, Object> albumInfoFromDb = getAlbumInfoByDb(albumId);
            // 4.2 同步数据到redis中
            stringRedisTemplate.opsForValue().set(cacheKey, JSONObject.toJSONString(albumInfoFromDb));
            // 4.3 释放锁
            stringRedisTemplate.delete(lockKey);
            // 4.4 返回查询结果
            return albumInfoFromDb;
        } else {
            // 4.2 未抢到锁，压测等待时间
            long timeout = 200;
            Thread.sleep(timeout);
//            String result = stringRedisTemplate.opsForValue().get(cacheKey);
//            return JSONObject.parseObject(result, Map.class);

            // 4.4 递归查询
            return opsDistributedCacheAndCacheHitV1(albumId);
        }
    }

    /**
     * 分布式缓存 + 布隆过滤器
     *
     * @param albumId
     * @return
     * @throws InterruptedException
     * @throws ExecutionException
     */
    private Map opsDistributedCacheAndBloomFilter(Long albumId) throws InterruptedException, ExecutionException {
        long startTime = System.currentTimeMillis();
        // 0. 查询分布式布隆过滤器查询
        boolean contains = rBloomFilter.contains(albumId);
        if (!contains) {
            return null;
        }

        // 1. 从分布式缓存中查询
        String cacheKey = "cache:info:albumId:" + albumId;
        String jsonFromCache = stringRedisTemplate.opsForValue().get(cacheKey);
        if (!StringUtils.isEmpty(jsonFromCache)) {
            // 2. 缓存命中 直接返回
            long endTime = System.currentTimeMillis();
            log.info("分布式缓存查询命中，耗时：{}ms", endTime - startTime);
            return JSONObject.parseObject(jsonFromCache, Map.class);
        }
        // 3. 缓存未命中 从数据库查询
        Map<String, Object> albumInfoFromDb = getAlbumInfoByDb(albumId);
        // 4. 将查询结果保存到缓存中 expire 10分钟
        stringRedisTemplate.opsForValue().set(cacheKey, JSONObject.toJSONString(albumInfoFromDb), 10, TimeUnit.MINUTES);
        // 5. 查询结果返回
        long endTime = System.currentTimeMillis();
        log.info("分布式缓存查询未命中，耗时：{}ms", endTime - startTime);
        return albumInfoFromDb;
    }

    /**
     * 分布式缓存 基本实现
     *
     * @param albumId
     * @return
     * @throws InterruptedException
     * @throws ExecutionException
     */
    private Map opsDistributedCache(Long albumId) throws InterruptedException, ExecutionException {
        long startTime = System.currentTimeMillis();
        // 1. 从分布式缓存中查询
        String cacheKey = "cache:info:albumId:" + albumId;
        String jsonFromCache = stringRedisTemplate.opsForValue().get(cacheKey);
        if (!StringUtils.isEmpty(jsonFromCache)) {
            // 2. 缓存命中 直接返回
            long endTime = System.currentTimeMillis();
            log.info("分布式缓存查询命中，耗时：{}ms", endTime - startTime);
            return JSONObject.parseObject(jsonFromCache, Map.class);
        }
        // 3. 缓存未命中 从数据库查询
        Map<String, Object> albumInfoFromDb = getAlbumInfoByDb(albumId);
        // 4. 将查询结果保存到缓存中 expire 10分钟
        stringRedisTemplate.opsForValue().set(cacheKey, JSONObject.toJSONString(albumInfoFromDb), 10, TimeUnit.MINUTES);
        // 5. 查询结果返回
        long endTime = System.currentTimeMillis();
        log.info("分布式缓存查询未命中，耗时：{}ms", endTime - startTime);
        return albumInfoFromDb;
    }

    /**
     * 本地缓存 + 本地布隆过滤器
     *
     * @param albumId
     * @return
     * @throws InterruptedException
     * @throws ExecutionException
     */
    private Map<String, Object> osLocalCacheAndBoomFilter(Long albumId) throws InterruptedException, ExecutionException {
        long startTime = System.currentTimeMillis();
        String cacheKey = "cache:info:albumId:" + albumId;
        // 0. 布隆过滤器判断是否存在
        boolean bloomFilterCheckFlag = longBloomFilter.mightContain(albumId);
        if (!bloomFilterCheckFlag) {
            // 布隆过滤器判断不存在，直接返回
            return null;
        }
        // 1. 从缓存中查询
        boolean b = localCache.containsKey(cacheKey);
        if (b) {
            // 2. 缓存命中 直接返回
            long endTime = System.currentTimeMillis();
            log.info("本地缓存查询命中，耗时：{}ms", endTime - startTime);
            return (Map<String, Object>) localCache.get(cacheKey);
        }
        // 3. 未命中缓存 从数据库查询
        Map<String, Object> albumInfoFromDb = getAlbumInfoByDb(albumId);
        // 4. 将查询结果保存到缓存中
        localCache.put(cacheKey, albumInfoFromDb);
        long endTime = System.currentTimeMillis();
        log.info("本地缓存查询未命中，耗时：{}ms", endTime - startTime);
        // 5. 查询结果返回
        return albumInfoFromDb;
    }

    /**
     * 本地缓存 基本实现
     *
     * @param albumId
     * @return
     * @throws InterruptedException
     * @throws ExecutionException
     */
    private Map<String, Object> osLocalCache(Long albumId) throws InterruptedException, ExecutionException {
        long startTime = System.currentTimeMillis();
        String cacheKey = "cache:info:albumId:" + albumId;
        // 1. 从缓存中查询
        boolean b = localCache.containsKey(cacheKey);
        if (b) {
            // 2. 缓存命中 直接返回
            long endTime = System.currentTimeMillis();
            log.info("本地缓存查询命中，耗时：{}ms", endTime - startTime);
            return (Map<String, Object>) localCache.get(cacheKey);
        }
        // 3. 未命中缓存 从数据库查询
        Map<String, Object> albumInfoFromDb = getAlbumInfoByDb(albumId);
        // 4. 将查询结果保存到缓存中
        localCache.put(cacheKey, albumInfoFromDb);
        long endTime = System.currentTimeMillis();
        log.info("本地缓存查询未命中，耗时：{}ms", endTime - startTime);
        // 5. 查询结果返回
        return albumInfoFromDb;
    }

    /**
     * 异步编排执行
     *
     * @param albumId
     * @return
     */
    @NotNull
    private Map<String, Object> getAlbumInfoByDb(Long albumId) {

        Map<String, Object> resultMap = new HashMap<>();

        CompletableFuture<Long> longCompletableFuture = CompletableFuture.supplyAsync(() -> {
            System.out.println("执行查询专辑微服务获取专辑信息使用的线程是：" + Thread.currentThread().getName());
            // 1. 远调查询专辑微服务 获取专辑基本信息
            Result<AlbumInfo> albumInfoResult = albumInfoFeignClient.getAlbumInfo(albumId);
            Assert.notNull(albumInfoResult, "远调查询专辑微服务，获取专辑基本信息失败");
            AlbumInfo albumInfoResultData = albumInfoResult.getData();
            resultMap.put("albumInfo", albumInfoResultData);
            return albumInfoResultData.getUserId();
        }, threadPoolExecutor);

        CompletableFuture<Void> albumInfoResultFuture = CompletableFuture.runAsync(() -> {
            System.out.println("执行查询专辑微服务获取专辑统计信息使用的线程是：" + Thread.currentThread().getName());
            // 2. 远调查询专辑微服务 获取专辑统计信息
            Result<AlbumStatVo> albumInfoStatResult = albumInfoFeignClient.getAlbumInfoStat(albumId);
            Assert.notNull(albumInfoStatResult, "远调查询专辑微服务，获取专辑统计信息失败");
            AlbumStatVo albumInfoStatResultData = albumInfoStatResult.getData();
            resultMap.put("albumStatVo", albumInfoStatResultData);
        }, threadPoolExecutor);

        CompletableFuture<Void> albumInfoCategoryResultFuture = CompletableFuture.runAsync(() -> {
            System.out.println("执行查询专辑微服务获取专辑的分类信息使用的线程是：" + Thread.currentThread().getName());
            // 3. 远调查询专辑微服务 获取专辑的分类信息
            Result<BaseCategoryView> albumInfoCategoryResult = albumInfoFeignClient.getAlbumInfoCategory(albumId);
            Assert.notNull(albumInfoCategoryResult, "远调查询专辑微服务，获取专辑的分类信息失败");
            BaseCategoryView albumInfoCategoryResultData = albumInfoCategoryResult.getData();
            resultMap.put("baseCategoryView", albumInfoCategoryResultData);
        }, threadPoolExecutor);

        CompletableFuture<Void> userInfoResultFuture = longCompletableFuture.thenAcceptAsync((userId) -> {
            System.out.println("执行查询用户微服务获取主播信息使用的线程是：" + Thread.currentThread().getName());
            // 4. 远调查询用户微服务 获取主播信息
//            Result<UserInfoVo> userInfoVoResult = userInfoFeignClient.getUserInfoVo(albumInfoResultData.getUserId());
            Result<UserInfoVo> userInfoVoResult = userInfoFeignClient.getUserInfoVo(userId);
            Assert.notNull(userInfoVoResult, "远调查询用户微服务，获取主播信息失败");
            UserInfoVo userInfoVoResultData = userInfoVoResult.getData();
            resultMap.put("announcer", userInfoVoResultData);
        }, threadPoolExecutor);

        // 5. 等待所有线程执行完成
        CompletableFuture<Void> future = CompletableFuture.allOf(longCompletableFuture, albumInfoResultFuture, albumInfoCategoryResultFuture, userInfoResultFuture);
//        future.join();
/*        // 5.2. 异步编排执行异常处理
        future.exceptionally(ex -> {
            log.error("异步编排执行异常：{}", ex);
            return null;
        });*/
        try {
            // 5.1. 等待所有线程执行完成
            future.get();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return resultMap;
    }

    @Override
    @SneakyThrows
    public void preHitRank() {

        // 1. 获取所有的 一级分类id
        Result<List<Long>> c1IdListResult = albumInfoFeignClient.getCategory1IdList();
        List<Long> c1IdListData = c1IdListResult.getData();
        Assert.notNull(c1IdListData, "一级分类id集合不存在");

        // 2. 遍历一级分类id集合
        for (Long c1Id : c1IdListData) {

            String[] dimensions = new String[]{"hotScore", "playStatNum", "subscribeStatNum", "buyStatNum", "commentStatNum"};

            for (String dimension : dimensions) {
                // 2.1. 根据一级分类id获取排行榜数据
                SearchRequest searchRequest = SearchRequest.of(b -> b
                        .index("albuminfo")
                        .query(qb -> qb
                                .term(tqb -> tqb
                                        .field("category1Id")
                                        .value(c1Id)))
                        .sort(sob -> sob.field(fsb -> fsb.field(dimension).order(SortOrder.Desc)))
                        .size(10));
                // 2.2. 根据排行榜数据获取专辑详情信息
                SearchResponse<AlbumInfoIndex> response = elasticsearchClient.search(searchRequest, AlbumInfoIndex.class);

                ArrayList<AlbumInfoIndex> albumInfoIndexList = new ArrayList<>();
                for (Hit<AlbumInfoIndex> hit : response.hits().hits()) {
                    AlbumInfoIndex source = hit.source();
                    albumInfoIndexList.add(source);
                }
                // 3. 存储到redis中
                String bigKey = RedisConstant.RANKING_KEY_PREFIX + c1Id;
                stringRedisTemplate.opsForHash().put(bigKey, dimension, JSONObject.toJSONString(albumInfoIndexList));
            }
        }
    }

    @Override
    public List<AlbumInfoIndexVo> findRankingList(Integer c1Id, String dimension) {

        String bigKey = RedisConstant.RANKING_KEY_PREFIX + c1Id;
        String resultStr = (String) stringRedisTemplate.opsForHash().get(bigKey, dimension);
        if (StringUtils.isEmpty(resultStr)) {
            return new ArrayList<AlbumInfoIndexVo>();
        }
        return JSONObject.parseArray(resultStr, AlbumInfoIndexVo.class);
    }

    @Override
    @Cacheable(cacheKey = "#album:info#{#args[0]}")
    public boolean cacheableSpELTest() {
        return false;
    }


    /**
     * 1. 串行执行
     * @param albumId
     * @return
     */
    /*@Override
    @SneakyThrows
    public Map<String, Object> getAlbumInfoByAlbumId(Long albumId) {

        Map<String, Object> resultMap = new HashMap<>();

            // 1. 远调查询专辑微服务 获取专辑信息
            Result<AlbumInfo> albumInfoResult = albumInfoFeignClient.getAlbumInfo(albumId);
            Assert.notNull(albumInfoResult, "远调查询专辑微服务获取专辑信息失败");
            AlbumInfo albumInfoResultData = albumInfoResult.getData();
            resultMap.put("albumInfo", albumInfoResultData);

            // 2. 远调查询专辑微服务 获取专辑信息
            Result<AlbumStatVo> albumInfoStatResult = albumInfoFeignClient.getAlbumInfoStat(albumId);
            Assert.notNull(albumInfoStatResult, "远调查询专辑微服务获取专辑统计信息失败");
            AlbumStatVo albumInfoStatResultData = albumInfoStatResult.getData();
            resultMap.put("albumStatVo", albumInfoStatResultData);

            Result<BaseCategoryView> albumInfoCategoryResult = albumInfoFeignClient.getAlbumInfoCategory(albumId);
            Assert.notNull(albumInfoCategoryResult, "远调查询专辑微服务获取专辑的分类信息失败");
            BaseCategoryView albumInfoCategoryResultData = albumInfoCategoryResult.getData();
            resultMap.put("baseCategoryView", albumInfoCategoryResultData);

            // 4. 远调查询用户微服务 获取主播信息
            Result<UserInfoVo> userInfoVoResult = userInfoFeignClient.getUserInfoVo(albumInfoResultData.getUserId());
            Assert.notNull(userInfoVoResult, "远调查询用户微服务获取主播信息失败");
            UserInfoVo userInfoVoResultData = userInfoVoResult.getData();
            resultMap.put("announcer", userInfoVoResultData);
        return resultMap;
    }*/

    /**
     * 2. new Thread() + join + ConcurrentHashMap + Runnable 方式实现
     * 也可以用new Thread() + FutureTask + Callable + join 方式实现
     * C D A 并发执行
     * A B 串行执行
     * tomcat 线程池和ABCD是串行的
     * 第一次耗时间： 354ms
     * 之后平均： 30ms
     * @param albumId
     */
/*    Map cmap = new ConcurrentHashMap<String , Long>();

    @Override
    @SneakyThrows
    public void onAlbumEs(Long albumId) {
        log.info("开始执行onAlbumEs方法，albumId={}", albumId);
        Long startTime = System.currentTimeMillis();
        // 构建索引文档
        AlbumInfoIndex albumInfoIndex = new AlbumInfoIndex();

        Thread threadD = new Thread(new Runnable() {
            @Override
            public void run() {
                // 4. 远调查询专辑微服务 获取专辑的统计信息
                Result<AlbumStatVo> albumStatVoResult = albumInfoFeignClient.getAlbumInfoStat(albumId);
                AlbumStatVo albumStatVoData = albumStatVoResult.getData();
                if (albumStatVoData == null) {
                    throw new TsException(201, "搜索微服务调用专辑微服务获取专辑统计信息失败");
                }
                // album_stat表中的字段
                Integer playStatNum = albumStatVoData.getPlayStatNum();
                Integer subscribeStatNum = albumStatVoData.getSubscribeStatNum();
                Integer buyStatNum = albumStatVoData.getBuyStatNum();
                Integer commentStatNum = albumStatVoData.getCommentStatNum();
                albumInfoIndex.setPlayStatNum(playStatNum);
                albumInfoIndex.setSubscribeStatNum(subscribeStatNum);
                albumInfoIndex.setBuyStatNum(buyStatNum);
                albumInfoIndex.setCommentStatNum(commentStatNum);

                // 热度评分字段 权重计算 根据专辑的播放量、订阅数、购买数、评论数 综合评分
//        Double hotScore = playStatNum * 0.4 + subscribeStatNum * 0.1 + buyStatNum * 0.3 + commentStatNum * 0.2;
                Double hotScore = new Random().nextDouble();
                albumInfoIndex.setHotScore(hotScore);
            }
        });
        threadD.start();

        Thread threadC = new Thread(new Runnable() {
            @Override
            public void run() {
                // 3. 远调查询专辑微服务 获取专辑的分类信息
                Result<BaseCategoryView> baseCategoryViewResult = albumInfoFeignClient.getAlbumInfoCategory(albumId);
                if (baseCategoryViewResult == null) {
                    throw new TsException(201, "远调查询专辑微服务获取专辑的分类信息失败");
                }
                BaseCategoryView baseCategoryViewData = baseCategoryViewResult.getData();
                // base_category1、base_category2、base_category3表中的字段
                albumInfoIndex.setCategory1Id(baseCategoryViewData.getCategory1Id());
                albumInfoIndex.setCategory2Id(baseCategoryViewData.getCategory2Id());
                albumInfoIndex.setCategory3Id(baseCategoryViewData.getCategory3Id());
            }
        });
        threadC.start();

        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                // 1. 远调查询专辑微服务 获取专辑基本信息
                Result<AlbumInfo> albumInfoResult = albumInfoFeignClient.getAlbumInfo(albumId);
                if (albumInfoResult == null) {
                    throw new TsException(201, "远调查询专辑微服务获取专辑基本信息失败");
                }
                AlbumInfo albumInfoResultData = albumInfoResult.getData();
                //拷贝基本信息到alInfoIndex对象中
                BeanUtils.copyProperties(albumInfoResultData, albumInfoIndex);

                // 设置album_info表中的字段
//        albumInfoIndex.setId(albumId);// 文档id
//        albumInfoIndex.setAlbumTitle(albumInfoResultData.getAlbumTitle());// 专辑标题
//        albumInfoIndex.setAlbumIntro(albumInfoResultData.getAlbumIntro());// 专辑简介
//        albumInfoIndex.setCoverUrl(albumInfoResultData.getCoverUrl());// 专辑封面图片
//        albumInfoIndex.setIncludeTrackCount(albumInfoResultData.getIncludeTrackCount());// 专辑包含的曲目数
//        albumInfoIndex.setPayType(albumInfoResultData.getPayType());// 付费类型
                albumInfoIndex.setIsFinished(albumInfoResultData.getIsFinished().toString());// 是否完结
                List<AttributeValueIndex> attributeValueIndexList = albumInfoResultData.getAlbumAttributeValueVoList().stream().map(albumAttributeValue -> {
                    AttributeValueIndex attributeValueIndex = new AttributeValueIndex();
                    attributeValueIndex.setAttributeId(albumAttributeValue.getAttributeId());
                    attributeValueIndex.setValueId(albumAttributeValue.getValueId());
                    return attributeValueIndex;
                }).collect(Collectors.toList());
                albumInfoIndex.setAttributeValueIndexList(attributeValueIndexList);// 专辑标签信息
                albumInfoIndex.setCreateTime(new Date());// 创建时间

                cmap.put("userId", albumInfoResultData.getUserId());// 用concurrentHashMap 存储userId，便于后续操作
            }
        });
        threadA.start();
        threadA.join(); // 让tomcat线程阻塞住，等待threadA执行完成之后再往下执行

        Thread threadB = new Thread(new Runnable() {
            @Override
            public void run() {
                Long userId = (Long) cmap.get("userId");// 从concurrentHashMap中获取userId
                // 2. 远调查询用户微服务 获取用户的基本信息
                // user_info表中的字段
                Result<UserInfoVo> userInfoVoResult = userInfoFeignClient.getUserInfoVo(userId);
                if (userInfoVoResult == null) {
                    throw new TsException(201, "远调查询用户微服务获取主播信息失败");
                }
                UserInfoVo userInfoVoResultData = userInfoVoResult.getData();
                albumInfoIndex.setAnnouncerName(userInfoVoResultData.getNickname());// 主播名称
            }
        });
        threadB.start();

        threadB.join();
        threadC.join();
        threadD.join();

        // 保存到ES
        AlbumInfoIndex flag = albumInfoRepository.save(albumInfoIndex);

        Long endTime = System.currentTimeMillis();
        log.info("专辑：{}存储到ES中{}耗时：{}ms", albumId, flag != null ? "成功" : "失败", endTime - startTime);
    }*/


    /**
     * 1. 传统方法
     * 第一次耗时间： 1461ms
     * 之后平均： 53ms
     *
     * @param albumId
     */
/*    @Override
    public void onAlbumEs(Long albumId) {
        log.info("开始执行onAlbumEs方法，albumId={}", albumId);
        Long startTime = System.currentTimeMillis();
        // 构建索引文档
        AlbumInfoIndex albumInfoIndex = new AlbumInfoIndex();

        // 1. 远调查询专辑微服务 获取专辑基本信息
        Result<AlbumInfo> albumInfoResult = albumInfoFeignClient.getAlbumInfo(albumId);
        if (albumInfoResult == null) {
            throw new TsException(201, "远调查询专辑微服务获取专辑基本信息失败");
        }
        AlbumInfo albumInfoResultData = albumInfoResult.getData();
        //拷贝基本信息到alInfoIndex对象中
        BeanUtils.copyProperties(albumInfoResultData, albumInfoIndex);

        // 设置album_info表中的字段
//        albumInfoIndex.setId(albumId);// 文档id
//        albumInfoIndex.setAlbumTitle(albumInfoResultData.getAlbumTitle());// 专辑标题
//        albumInfoIndex.setAlbumIntro(albumInfoResultData.getAlbumIntro());// 专辑简介
//        albumInfoIndex.setCoverUrl(albumInfoResultData.getCoverUrl());// 专辑封面图片
//        albumInfoIndex.setIncludeTrackCount(albumInfoResultData.getIncludeTrackCount());// 专辑包含的曲目数
//        albumInfoIndex.setPayType(albumInfoResultData.getPayType());// 付费类型
        albumInfoIndex.setIsFinished(albumInfoResultData.getIsFinished().toString());// 是否完结
        List<AttributeValueIndex> attributeValueIndexList = albumInfoResultData.getAlbumAttributeValueVoList().stream().map(albumAttributeValue -> {
            AttributeValueIndex attributeValueIndex = new AttributeValueIndex();
            attributeValueIndex.setAttributeId(albumAttributeValue.getAttributeId());
            attributeValueIndex.setValueId(albumAttributeValue.getValueId());
            return attributeValueIndex;
        }).collect(Collectors.toList());
        albumInfoIndex.setAttributeValueIndexList(attributeValueIndexList);// 专辑标签信息
        albumInfoIndex.setCreateTime(new Date());// 创建时间

        // 2. 远调查询用户微服务 获取用户的基本信息
        // user_info表中的字段
        Result<UserInfoVo> userInfoVoResult = userInfoFeignClient.getUserInfoVo(albumInfoResultData.getUserId());
        if (userInfoVoResult == null) {
            throw new TsException(201, "远调查询用户微服务获取主播信息失败");
        }
        UserInfoVo userInfoVoResultData = userInfoVoResult.getData();
        albumInfoIndex.setAnnouncerName(userInfoVoResultData.getNickname());// 主播名称

        // 3. 远调查询专辑微服务 获取专辑的分类信息
        Result<BaseCategoryView> baseCategoryViewResult = albumInfoFeignClient.getAlbumInfoCategory(albumId);
        if (baseCategoryViewResult == null) {
            throw new TsException(201, "远调查询专辑微服务获取专辑的分类信息失败");
        }
        BaseCategoryView baseCategoryViewData = baseCategoryViewResult.getData();
        // base_category1、base_category2、base_category3表中的字段
        albumInfoIndex.setCategory1Id(baseCategoryViewData.getCategory1Id());
        albumInfoIndex.setCategory2Id(baseCategoryViewData.getCategory2Id());
        albumInfoIndex.setCategory3Id(baseCategoryViewData.getCategory3Id());

        // 4. 远调查询专辑微服务 获取专辑的统计信息

        Result<AlbumStatVo> albumStatVoResult = albumInfoFeignClient.getAlbumInfoStat(albumId);
        AlbumStatVo albumStatVoData = albumStatVoResult.getData();
        if (albumStatVoData == null) {
            throw new TsException(201, "搜索微服务调用专辑微服务获取专辑统计信息失败");
        }
        // album_stat表中的字段
        Integer playStatNum = albumStatVoData.getPlayStatNum();
        Integer subscribeStatNum = albumStatVoData.getSubscribeStatNum();
        Integer buyStatNum = albumStatVoData.getBuyStatNum();
        Integer commentStatNum = albumStatVoData.getCommentStatNum();
        albumInfoIndex.setPlayStatNum(playStatNum);
        albumInfoIndex.setSubscribeStatNum(subscribeStatNum);
        albumInfoIndex.setBuyStatNum(buyStatNum);
        albumInfoIndex.setCommentStatNum(commentStatNum);

        // 热度评分字段 权重计算 根据专辑的播放量、订阅数、购买数、评论数 综合评分
//        Double hotScore = playStatNum * 0.4 + subscribeStatNum * 0.1 + buyStatNum * 0.3 + commentStatNum * 0.2;
        Double hotScore = new Random().nextDouble();
        albumInfoIndex.setHotScore(hotScore);

        // 保存到ES
        AlbumInfoIndex flag = albumInfoRepository.save(albumInfoIndex);

        Long endTime = System.currentTimeMillis();
        log.info("专辑：{}存储到ES中{}耗时：{}ms", albumId, flag != null ? "成功" : "失败", endTime - startTime);
    }*/
}
